#pragma once
#include <string>
#include <spdlog/spdlog.h>
#include <SQLiteCpp/SQLiteCpp.h>

#include "kungfu/wingchun/msg.h"

using namespace kungfu::wingchun::msg;

namespace kungfu
{
    namespace wingchun 
    {
        namespace book
        {
            class BookOrderMapper
            {
            public:
                BookOrderMapper(const std::string& file_name): db_(file_name.c_str(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {
                    create_table_if_not_exist();
                };

                std::list<msg::data::Order> get_book_orders_info(const std::string account_id)
                {
                    std::list<msg::data::Order> orders;
                    SQLite::Statement query(db_, "select * from 'order' WHERE account_id = ?");

                    try
                    {
                        query.bind(1, account_id);
                        if (query.executeStep())
                        {
                            msg::data::Order result = {};

                            result.order_id = query.getColumn(0).getInt64();
                            
                            result.insert_time = query.getColumn(1).getInt64();
                            result.update_time = query.getColumn(2).getInt64();

                            strcpy(result.trading_day, query.getColumn(3).getString().c_str());
                            strcpy(result.instrument_id, query.getColumn(4).getString().c_str());
                            strcpy(result.exchange_id, query.getColumn(5).getString().c_str());
                            strcpy(result.source_id, query.getColumn(6).getString().c_str());
                            strcpy(result.account_id, query.getColumn(7).getString().c_str());
                            strcpy(result.client_id, query.getColumn(8).getString().c_str());

                            result.instrument_type = InstrumentType(query.getColumn(9).getInt());

                            result.limit_price = query.getColumn(10).getDouble();
                            result.frozen_price = query.getColumn(11).getDouble();

                            result.volume = query.getColumn(12).getInt64();
                            result.volume_traded = query.getColumn(13).getInt64();
                            result.volume_left = query.getColumn(14).getInt64();
                            result.tax = query.getColumn(15).getDouble();
                            result.commission = query.getColumn(16).getDouble();

                            result.status = OrderStatus(query.getColumn(17).getInt());
                            result.error_id = query.getColumn(18).getInt();
                            strcpy(result.error_msg, query.getColumn(19).getString().c_str());

                            result.side = Side(query.getColumn(20).getInt());
                            result.offset = Offset(query.getColumn(21).getInt());
                            result.hedge_flag = HedgeFlag(query.getColumn(22).getInt());
                            result.price_type = PriceType(query.getColumn(23).getInt());
                            result.volume_condition = VolumeCondition(query.getColumn(24).getInt());
                            result.time_condition = TimeCondition(query.getColumn(25).getInt());

                            result.parent_id = query.getColumn(26).getInt64();

                            orders.push_back(result);
                        }
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("exception: {}", e.what());
                    }
                    return orders;
                };

                void create_table_if_not_exist()
                {
                    std::string sql = "CREATE TABLE IF NOT EXISTS 'order'(\n"
                        "                        order_id CHAR(50)      PRIMARY KEY NOT NULL,\n"
                        "                        insert_time            INTEGER,  \n"
                        "                        update_time            INTEGER,  \n"
                        "                        trading_day            CHAR(50), \n"
                        "                        instrument_id          CHAR(50), \n"
                        "                        exchange_id            CHAR(50), \n"
                        "                        source_id              CHAR(50), \n"
                        "                        account_id             CHAR(50), \n"
                        "                        client_id              CHAR(50), \n"
                        "                        instrument_type        INTEGER,  \n"
                        "                        limit_price            FLOAT,    \n"
                        "                        frozen_price           FLOAT,    \n"
                        "                        volume                 INTEGER,  \n"
                        "                        volume_traded          INTEGER,  \n"
                        "                        volume_left            INTEGER,  \n"
                        "                        tax                    FLOAT,    \n"
                        "                        commission             FLOAT,    \n"
                        "                        status                 INTEGER,  \n"
                        "                        error_id               INTEGER,  \n"
                        "                        error_msg              CHAR(128),\n"
                        "                        side                   INTEGER,  \n"
                        "                        offset                 INTEGER,  \n"
                        "                        hedge_flag             INTEGER,  \n"
                        "                        price_type             INTEGER,  \n"
                        "                        volume_condition       INTEGER,  \n"
                        "                        time_condition         INTEGER,  \n"
                        "                        parent_id              CHAR(50));";
                    try
                    {
                        db_.exec(sql);
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("failed to create table, sql: {}, exception: {}", sql.c_str(), e.what());
                    }
                }
            private:
                SQLite::Database db_;
            };

            class BookPositionMapper
            {
            public:
                BookPositionMapper(const std::string& file_name) : db_(file_name.c_str(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {
                    create_table_if_not_exist();
                };

                std::list<msg::data::Position> get_book_positions_info(const std::uint32_t uid)
                {
                    std::list<msg::data::Position> orders;
                    SQLite::Statement query(db_, "select * from position WHERE holder_uid = ?");

                    try
                    {
                        query.bind(1, uid);
                        if (query.executeStep())
                        {
                            msg::data::Position result = {};

                            result.update_time = query.getColumn(1).getInt64();

                            strcpy(result.trading_day, query.getColumn(2).getString().c_str());
                            strcpy(result.instrument_id, query.getColumn(3).getString().c_str());
                            strcpy(result.exchange_id, query.getColumn(4).getString().c_str());
                            result.instrument_type = InstrumentType(query.getColumn(5).getInt());
                            result.holder_uid = query.getColumn(6).getInt64();
                            result.ledger_category = LedgerCategory(query.getColumn(7).getInt());

                            strcpy(result.source_id, query.getColumn(8).getString().c_str());
                            strcpy(result.account_id, query.getColumn(9).getString().c_str());
                            strcpy(result.client_id, query.getColumn(10).getString().c_str());
                            
                            result.direction = Direction(query.getColumn(11).getInt());

                            result.volume = query.getColumn(12).getInt64();
                            result.yesterday_volume = query.getColumn(13).getInt64();

                            result.frozen_total = query.getColumn(14).getInt64();
                            result.frozen_yesterday = query.getColumn(15).getInt64();

                            result.last_price = query.getColumn(16).getDouble();
                            result.avg_open_price = query.getColumn(17).getDouble();

                            result.position_cost_price = query.getColumn(18).getDouble();
                            result.close_price = query.getColumn(19).getDouble();
                            result.pre_close_price = query.getColumn(20).getDouble();

                            result.settlement_price = query.getColumn(21).getDouble();
                            result.pre_settlement_price = query.getColumn(22).getDouble();

                            result.margin = query.getColumn(23).getDouble();
                            result.position_pnl = query.getColumn(24).getDouble();
                            result.close_pnl = query.getColumn(25).getDouble();
                            result.realized_pnl = query.getColumn(26).getDouble();
                            result.unrealized_pnl = query.getColumn(27).getDouble();

                            orders.push_back(result);
                        }
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("exception: {}", e.what());
                    }
                    return orders;
                };

                std::list<msg::data::Position> update_book_positions_info(const std::uint32_t uid)
                {
                    std::list<msg::data::Position> orders;
                    SQLite::Statement query(db_, "select * from position WHERE holder_uid = ?");

                    try
                    {
                        query.bind(1, uid);
                        if (query.executeStep())
                        {
                            msg::data::Position result = {};

                            result.update_time = query.getColumn(1).getInt64();

                            strcpy(result.trading_day, query.getColumn(2).getString().c_str());
                            strcpy(result.instrument_id, query.getColumn(3).getString().c_str());
                            strcpy(result.exchange_id, query.getColumn(4).getString().c_str());
                            result.instrument_type = InstrumentType(query.getColumn(5).getInt());
                            result.holder_uid = query.getColumn(6).getInt64();
                            result.ledger_category = LedgerCategory(query.getColumn(7).getInt());

                            strcpy(result.source_id, query.getColumn(8).getString().c_str());
                            strcpy(result.account_id, query.getColumn(9).getString().c_str());
                            strcpy(result.client_id, query.getColumn(10).getString().c_str());

                            result.direction = Direction(query.getColumn(11).getInt());

                            result.volume = query.getColumn(12).getInt64();
                            result.yesterday_volume = query.getColumn(13).getInt64();

                            result.frozen_total = query.getColumn(14).getInt64();
                            result.frozen_yesterday = query.getColumn(15).getInt64();

                            result.last_price = query.getColumn(16).getDouble();
                            result.avg_open_price = query.getColumn(17).getDouble();

                            result.position_cost_price = query.getColumn(18).getDouble();
                            result.close_price = query.getColumn(19).getDouble();
                            result.pre_close_price = query.getColumn(20).getDouble();

                            result.settlement_price = query.getColumn(21).getDouble();
                            result.pre_settlement_price = query.getColumn(22).getDouble();

                            result.margin = query.getColumn(23).getDouble();
                            result.position_pnl = query.getColumn(24).getDouble();
                            result.close_pnl = query.getColumn(25).getDouble();
                            result.realized_pnl = query.getColumn(26).getDouble();
                            result.unrealized_pnl = query.getColumn(27).getDouble();

                            orders.push_back(result);
                        }
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("exception: {}", e.what());
                    }
                    return orders;
                };

                void create_table_if_not_exist()
                {
                    std::string sql = "CREATE TABLE IF NOT EXISTS position(\n"
                                      " 	id CHAR(50) PRIMARY KEY NOT NULL,\n"
                                      " 	update_time             INTEGER, \n"
                                      " 	trading_day             CHAR(50),\n"
                                      " 	instrument_id           CHAR(50),\n"
                                      " 	instrument_type         INTEGER, \n"
                                      " 	exchange_id             CHAR(50),\n"
                                      " 	holder_uid              INTEGER, \n"
                                      " 	ledger_category         INTEGER, \n"
                                      " 	account_id              CHAR(50),\n"
                                      " 	source_id               CHAR(50),\n"
                                      " 	client_id               CHAR(50),\n"
                                      " 	direction               INTEGER, \n"
                                      " 	volume                  INTEGER, \n"
                                      " 	yesterday_volume        INTEGER, \n"
                                      " 	frozen_total            INTEGER, \n"
                                      " 	frozen_yesterday        INTEGER, \n"
                                      " 	last_price              FLOAT,\n"
                                      " 	avg_open_price          FLOAT,\n"
                                      " 	position_cost_price     FLOAT,\n"
                                      " 	close_price             FLOAT,\n"
                                      " 	pre_close_price         FLOAT,\n"
                                      " 	settlement_price        FLOAT,\n"
                                      " 	pre_settlement_price    FLOAT,\n"
                                      " 	margin                  FLOAT,\n"
                                      " 	position_pnl            FLOAT,\n"
                                      " 	close_pnl               FLOAT,\n"
                                      " 	realized_pnl            FLOAT,\n"
                                      " 	unrealized_pnl          FLOAT,\n"
                                      " 	margin_ratio            FLOAT,\n"
                                      " 	contract_multiplier     INTEGER);";
                    try
                    {
                        db_.exec(sql);
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("failed to create table, sql: {}, exception: {}", sql.c_str(), e.what());
                    }
                }
            private:
                SQLite::Database db_;
            };

            class BookAssetMapper
            {
            public:
                BookAssetMapper(const std::string& file_name) : db_(file_name.c_str(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {
                    create_table_if_not_exist();
                };

                msg::data::Asset get_book_assets_info(const std::uint32_t uid)
                {
                    msg::data::Asset result = {};
                    SQLite::Statement query(db_, "select * from asset where holder_uid = ?");

                    try
                    {
                        query.bind(1, uid);
                        if (query.executeStep())
                        {
                            strcpy(result.trading_day, query.getColumn(0).getString().c_str());
                            result.update_time = query.getColumn(1).getInt64();
                            result.holder_uid = query.getColumn(2).getInt();
                            result.ledger_category = LedgerCategory(query.getColumn(3).getInt());

                            strcpy(result.account_id, query.getColumn(4).getString().c_str());
                            strcpy(result.source_id, query.getColumn(5).getString().c_str());
                            strcpy(result.client_id, query.getColumn(6).getString().c_str());

                            result.initial_equity = query.getColumn(7).getDouble();
                            result.static_equity = query.getColumn(8).getDouble();
                            result.dynamic_equity = query.getColumn(9).getDouble();
                            result.realized_pnl = query.getColumn(10).getDouble();
                            result.unrealized_pnl = query.getColumn(11).getDouble();
                            result.avail = query.getColumn(12).getDouble();
                            result.market_value = query.getColumn(13).getDouble();
                            result.margin = query.getColumn(14).getDouble();
                            result.accumulated_fee = query.getColumn(15).getDouble();
                            result.intraday_fee = query.getColumn(16).getDouble();
                            result.frozen_cash = query.getColumn(17).getDouble();
                            result.frozen_margin = query.getColumn(18).getDouble();
                            result.frozen_fee = query.getColumn(19).getDouble();
                            result.position_pnl = query.getColumn(20).getDouble();
                            result.close_pnl = query.getColumn(21).getDouble();
                        }
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("exception: {}", e.what());
                    }
                    return result;
                };

                void update_sys_id(int front_id, int session_id, const char* order_ref, const char* exchange_id, const char* order_sys_id)
                {
                    SQLite::Statement query(db_, "update asset SET exchange_id = ?, order_sys_id = ? WHERE front_id = ? AND session_id = ? AND order_ref = ?");
                    try
                    {
                        query.bind(1, exchange_id);
                        query.bind(2, order_sys_id);
                        query.bind(3, front_id);
                        query.bind(4, session_id);
                        query.bind(5, order_ref);
                        query.exec();

                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("exception: {}", e.what());
                    }
                }

                void create_table_if_not_exist()
                {
                    std::string sql = "CREATE TABLE IF NOT EXISTS asset( \n"
                        "	                    trading_day     CHAR(50),\n"
                        "	                    update_time     INTEGER, \n"
                        "	                    holder_uid      INTEGER PRIMARY KEY NOT NULL,\n"
                        "	                    ledger_category INTEGER, \n"
                        "	                    account_id      CHAR(50),\n"
                        "	                    source_id       CHAR(50),\n"
                        "	                    client_id       CHAR(50),\n"
                        "	                    initial_equity  FLOAT,\n"
                        "	                    static_equity   FLOAT,\n"
                        "	                    dynamic_equity  FLOAT,\n"
                        "	                    realized_pnl    FLOAT,\n"
                        "	                    unrealized_pnl  FLOAT,\n"
                        "	                    avail           FLOAT,\n"
                        "	                    market_value    FLOAT,\n"
                        "	                    margin          FLOAT,\n"
                        "	                    accumulated_fee FLOAT,\n"
                        "	                    intraday_fee    FLOAT,\n"
                        "	                    frozen_cash     FLOAT,\n"
                        "	                    frozen_margin   FLOAT,\n"
                        "	                    frozen_fee      FLOAT,\n"
                        "	                    position_pnl    FLOAT,\n"
                        "	                    close_pnl       FLOAT);";
                    try
                    {
                        db_.exec(sql);
                    }
                    catch (std::exception& e)
                    {
                        SPDLOG_ERROR("failed to create table, sql: {}, exception: {}", sql.c_str(), e.what());
                    }
                }
            private:
                SQLite::Database db_;
            };
        }
    }
}